* When an image is changed, invalidate pages that use it.
authorTim Starling <tstarling@users.mediawiki.org>
Sun, 17 Apr 2005 08:30:15 +0000 (08:30 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Sun, 17 Apr 2005 08:30:15 +0000 (08:30 +0000)
* Introduced image "broken links" allowing the user to quickly upload an image with that name
* "Upload a new version of this image" link from the image description page

includes/Article.php
includes/Image.php
includes/ImagePage.php
includes/Linker.php
includes/SquidUpdate.php
languages/Language.php

index 4b8b92d..ff221b2 100644 (file)
@@ -1713,11 +1713,8 @@ class Article {
                                $this->mTitle->getInternalURL(),
                                $this->mTitle->getInternalURL( 'history' )
                        );
-                       foreach ( $linksTo as $linkTo ) {
-                               $urls[] = $linkTo->getInternalURL();
-                       }
 
-                       $u = new SquidUpdate( $urls );
+                       $u = SquidUpdate::newFromTitles( $linksTo, $urls );
                        array_push( $wgPostCommitUpdateList, $u );
 
                }
index e081739..57d81b8 100644 (file)
@@ -944,7 +944,7 @@ class Image
         */
        function recordUpload( $oldver, $desc, $copyStatus = '', $source = '' ) {
                global $wgUser, $wgLang, $wgTitle, $wgOut, $wgDeferredUpdateList;
-               global $wgUseCopyrightUpload;
+               global $wgUseCopyrightUpload, $wgUseSquid, $wgPostCommitUpdateList;
 
                $fname = 'Image::recordUpload';
                $dbw =& wfGetDB( DB_MASTER );
@@ -954,7 +954,7 @@ class Image
                        wfDebugDieBacktrace( 'Database schema not up to date, please run maintenance/archives/patch-image_name_unique.sql' );
                }
 
-               // Delete thumbnails and refresh the cache
+               // Delete thumbnails and refresh the metadata cache
                $this->purgeCache();
 
                // Fail now if the image isn't there
@@ -970,7 +970,7 @@ class Image
                        $textdesc = $desc;
                }
 
-               $now = wfTimestampNow();
+               $now = $dbw->timestamp();
 
                # Test to see if the row exists using INSERT IGNORE
                # This avoids race conditions by locking the row until the commit, and also
@@ -983,13 +983,14 @@ class Image
                                'img_height' => $this->height,
                                'img_bits' => $this->bits,
                                'img_type' => $this->type,
-                               'img_timestamp' => $dbw->timestamp($now),
+                               'img_timestamp' => $now,
                                'img_description' => $desc,
                                'img_user' => $wgUser->getID(),
                                'img_user_text' => $wgUser->getName(),
                        ), $fname, 'IGNORE' 
                );
                $descTitle = $this->getTitle();
+               $purgeURLs = array();
 
                if ( $dbw->affectedRows() ) {
                        # Successfully inserted, this is a new image
@@ -1026,7 +1027,7 @@ class Image
                                        'img_height' => $this->height,
                                        'img_bits' => $this->bits,
                                        'img_type' => $this->type,
-                                       'img_timestamp' => $dbw->timestamp(),
+                                       'img_timestamp' => $now,
                                        'img_user' => $wgUser->getID(),
                                        'img_user_text' => $wgUser->getName(),
                                        'img_description' => $desc,
@@ -1037,14 +1038,59 @@ class Image
                        
                        # Invalidate the cache for the description page
                        $descTitle->invalidateCache();
+                       $purgeURLs[] = $descTitle->getInternalURL();
                }
 
+               # Invalidate cache for all pages using this image
+               $linksTo = $this->getLinksTo();
+               
+               if ( $wgUseSquid ) {
+                       $u = SquidUpdate::newFromTitles( $linksTo, $purgeURLs );
+                       array_push( $wgPostCommitUpdateList, $u );
+               }
+               Title::touchArray( $linksTo );
+               
                $log = new LogPage( 'upload' );
                $log->addEntry( 'upload', $descTitle, $desc );
 
                return true;
        }
-       
+
+       /**
+        * Get an array of Title objects which are articles which use this image
+        * Also adds their IDs to the link cache
+        * 
+        * This is mostly copied from Title::getLinksTo()
+        */
+       function getLinksTo( $options = '' ) {
+               global $wgLinkCache;
+               $fname = 'Image::getLinksTo';
+               wfProfileIn( $fname );
+               
+               if ( $options ) {
+                       $db =& wfGetDB( DB_MASTER );
+               } else {
+                       $db =& wfGetDB( DB_SLAVE );
+               }
+
+               extract( $db->tableNames( 'page', 'imagelinks' ) );
+               $encName = $db->addQuotes( $this->name );
+               $sql = "SELECT page_namespace,page_title,page_id FROM $page,$imagelinks WHERE page_id=il_from AND il_to=$encName $options";
+               $res = $db->query( $sql, $fname );
+               
+               $retVal = array();
+               if ( $db->numRows( $res ) ) {
+                       while ( $row = $db->fetchObject( $res ) ) {
+                               if ( $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title ) ) {
+                                       $wgLinkCache->addGoodLink( $row->page_id, $titleObj->getPrefixedDBkey() );
+                                       $retVal[] = $titleObj;
+                               }
+                       }
+               }
+               $db->freeResult( $res );
+               return $retVal;
+       }
+
 } //class
 
 
index c6eed1e..ee93cfb 100644 (file)
@@ -17,28 +17,39 @@ require_once( 'Image.php' );
  */
 class ImagePage extends Article {
 
-       /* private */ var $img;  // Image object this page is shown for. Initialized in openShowImage, not
-                                // available in doDelete etc.
-
+       /* private */ var $img;  // Image object this page is shown for
+       
        function view() {
-               global $wgUseExternalEditor;
-               if( $this->mTitle->getNamespace() == NS_IMAGE ) {
-                       $this->openShowImage();
-               }
+               global $wgUseExternalEditor, $wgOut;
 
-               Article::view();
-               if($wgUseExternalEditor) {
-                       $this->externalEditorLink();
-               }
-               
-               # If the article we've just shown is in the "Image" namespace,
-               # follow it with the history list and link list for the image
-               # it describes.
+               $this->img  = new Image( $this->mTitle );
 
-               if( $this->mTitle->getNamespace() == NS_IMAGE ) {
+               if( $this->mTitle->getNamespace() == NS_IMAGE  ) {
+                       $this->openShowImage();
+                       
+                       # No need to display noarticletext, we use our own message, output in openShowImage()
+                       if ( $this->getID() ) {
+                               Article::view();
+                       } else {
+                               # Just need to set the right headers
+                               $wgOut->setArticleFlag( true );
+                               $wgOut->setRobotpolicy( 'index,follow' );
+                               $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
+                               $wgOut->addMetaTags();
+                               $this->viewUpdates();
+                       }
+                       
+                       if ( $this->img->exists() ) {
+                               $this->uploadNewVersionLink();
+                               if ( $wgUseExternalEditor && $this->img->exists() ) {
+                                       $this->externalEditorLink();
+                               }
+                       }
                        $this->closeShowImage();
                        $this->imageHistory();
                        $this->imageLinks();
+               } else {
+                       Article::view();
                }
        }
 
@@ -46,8 +57,7 @@ class ImagePage extends Article {
        {
                global $wgOut, $wgUser, $wgImageLimits, $wgRequest, 
                       $wgUseImageResize, $wgRepositoryBaseUrl, 
-                      $wgUseExternalEditor;
-               $this->img  = new Image( $this->mTitle );
+                      $wgUseExternalEditor, $wgServer;
                $full_url  = $this->img->getViewURL();
                $anchoropen = '';
                $anchorclose = '';
@@ -63,12 +73,9 @@ class ImagePage extends Article {
                $max = $wgImageLimits[$sizeSel];
                $maxWidth = $max[0];
                $maxHeight = $max[1];
-
+               $sk = $wgUser->getSkin();
 
                if ( $this->img->exists() ) {
-
-                       $sk = $wgUser->getSkin();
-                       
                        if ( $this->img->getType() != '' ) {
                                # image
                                $width = $this->img->getWidth();
@@ -112,9 +119,24 @@ class ImagePage extends Article {
                                $wgOut->addWikiText($sharedtext);
                        }
                        
+               } else {
+                       # Image does not exist
+                       $wgOut->addWikiText( wfMsg( 'noimage', $this->getUploadUrl() ) );
                }
        }
        
+       function getUploadUrl() {
+               global $wgServer;
+               $uploadTitle = Title::makeTitle( NS_SPECIAL, 'Upload' );
+               return $wgServer . $uploadTitle->getLocalUrl( 'wpDestFile=' . urlencode( $this->img->getName() ) );
+       }
+
+
+       function uploadNewVersionLink() {
+               global $wgOut;
+               $wgOut->addWikiText( wfMsg( 'uploadnewversion', $this->getUploadUrl() ) );
+       }
+
        function externalEditorLink()
        {
                global $wgUser,$wgOut;
@@ -220,6 +242,8 @@ class ImagePage extends Article {
                        return;
                }
                
+               $this->img  = new Image( $this->mTitle );
+               
                # Deleting old images doesn't require confirmation
                if ( !is_null( $oldimage ) || $confirm ) {
                        if( $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $oldimage ) ) {
@@ -243,7 +267,7 @@ class ImagePage extends Article {
        function doDelete()
        {
                global $wgOut, $wgUser, $wgContLang, $wgRequest;
-               global $wgUseSquid, $wgInternalServer, $wgDeferredUpdateList;
+               global $wgUseSquid, $wgInternalServer, $wgPostCommitUpdateList;
                $fname = 'ImagePage::doDelete';
 
                $reason = $wgRequest->getVal( 'wpReason' );
@@ -291,17 +315,18 @@ class ImagePage extends Article {
                        $dbw->delete( 'image', array( 'img_name' => $image ) );
                        $res = $dbw->select( 'oldimage', array( 'oi_archive_name' ), array( 'oi_name' => $image ) );                    
 
+                       # Purge archive URLs from the squid
                        $urlArr = Array();
                        while ( $s = $dbw->fetchObject( $res ) ) {
                                $this->doDeleteOldImage( $s->oi_archive_name );
                                $urlArr[] = $wgInternalServer.wfImageArchiveUrl( $s->oi_archive_name );
                        }       
-                       
-                       # Defer purging of archived URLs
+
+                       # And also the HTML of all pages using this image
+                       $linksTo = $this->img->getLinksTo();
                        if ( $wgUseSquid ) {
-                               /* this needs to be done after LinksUpdate */
-                               $u = new SquidUpdate( $urlArr );
-                               array_push( $wgDeferredUpdateList, $u );
+                               $u = SquidUpdate::newFromTitles( $linksTo, $urlArr );
+                               array_push( $wgPostCommitUpdateList, $u );
                        }
                        
                        $dbw->delete( 'oldimage', array( 'oi_name' => $image ) );
@@ -312,9 +337,13 @@ class ImagePage extends Article {
                        $article = new Article( $this->mTitle );
                        $article->doDeleteArticle( $reason ); # ignore errors
 
+                       # Invalidate parser cache and client cache for pages using this image
+                       # This is left until relatively late to reduce lock time
+                       Title::touchArray( $linksTo );
+
                        /* Delete thumbnails and refresh image metadata cache */
-                       $imgObj = new Image( $this->mTitle );
-                       $imgObj->purgeCache();
+                       $this->img->purgeCache();
+
 
                        $deleted = $image;
                }
index a5c7a2b..f89a920 100644 (file)
@@ -537,8 +537,8 @@ class Linker {
 
                $u = $nt->escapeLocalURL();
                if ( $url == '' ) {
-                       $s = wfMsg( 'missingimage', $img->getName() );
-                       $s .= "<br />{$alt}<br />{$url}<br />\n";
+                       $s = $this->makeBrokenImageLinkObj( $img->getTitle() );
+                       //$s .= "<br />{$alt}<br />{$url}<br />\n";
                } else {
                        $s = '<a href="'.$u.'" class="image" title="'.$alt.'">' .
                                 '<img src="'.$url.'" alt="'.$alt.'" longdesc="'.$u.'" /></a>';
@@ -617,7 +617,7 @@ class Linker {
 
                $s = "<div class=\"thumb t{$align}\"><div style=\"width:{$oboxwidth}px;\">";
                if ( $thumbUrl == '' ) {
-                       $s .= wfMsg( 'missingimage', $img->getName() );
+                       $s .= $this->makeBrokenImageLinkObj( $img->getTitle );
                        $zoomicon = '';
                } else {
                        $s .= '<a href="'.$u.'" class="internal" title="'.$alt.'">'.
@@ -636,7 +636,45 @@ class Linker {
                $s .= '  <div class="thumbcaption" '.$textalign.'>'.$zoomicon.$label."</div></div></div>";
                return str_replace("\n", ' ', $s);
        }
+       
+       /**
+        * Pass a title object, not a title string
+        */
+       function makeBrokenImageLinkObj( &$nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
+               # Fail gracefully
+               if ( ! isset($nt) ) {
+                       # wfDebugDieBacktrace();
+                       return "<!-- ERROR -->{$prefix}{$text}{$trail}";
+               }
+
+               $fname = 'Skin::makeBrokenImageLinkObj';
+               wfProfileIn( $fname );
+
+               $q = 'wpDestFile=' . urlencode( $nt->getDBkey() );
+               if ( '' != $query ) {
+                       $q .= "&$query";
+               }
+               $uploadTitle = Title::makeTitle( NS_SPECIAL, 'Upload' );
+               $url = $uploadTitle->escapeLocalURL( $q );
+
+               if ( '' == $text ) {
+                       $text = htmlspecialchars( $nt->getPrefixedText() );
+               }
+               $style = $this->getInternalLinkAttributesObj( $nt, $text, "yes" );
 
+               $inside = '';
+               if ( '' != $trail ) {
+                       if ( preg_match( $this->linktrail, $trail, $m ) ) {
+                               $inside = $m[1];
+                               $trail = $m[2];
+                       }
+               }
+               $s = "<a href=\"{$url}\"{$style}>{$prefix}{$text}{$inside}</a>{$trail}";
+
+               wfProfileOut( $fname );
+               return $s;
+       }
+       
        /** @todo document */
        function makeMediaLink( $name, $url, $alt = '' ) {
                $nt = Title::makeTitleSafe( NS_IMAGE, $name );
index 0bcb61f..484f26d 100644 (file)
@@ -76,6 +76,13 @@ class SquidUpdate {
                return new SquidUpdate( $blurlArr );
        }
 
+       /* static */ function newFromTitles( &$titles, $urlArr = array() ) {
+               foreach ( $titles as $title ) {
+                       $urlArr[] = $title->getInternalURL();
+               }
+               return new SquidUpdate( $urlArr );
+       }
+
        /* static */ function newSimplePurge( &$title ) {
                $urlArr = $title->getSquidURLs();
                return new SquidUpdate( $blurlArr );
index 190a14e..dca90b4 100644 (file)
@@ -1056,6 +1056,14 @@ this old version, (rev) = revert to this old version.
 'nolinkstoimage' => 'There are no pages that link to this image.',
 'sharedupload' => 'This file is a shared upload and may be used by other projects.',
 'shareduploadwiki' => 'Please see the [$1 image description page] for further information.',
+'noimage'       => "No image by this name exists.
+
+<div class=\"editExternally\">
+'''[$1 Upload this image]'''
+</div><br clear=all />",
+'uploadnewversion' => "<div class=\"editExternally\">
+[$1 Upload a new version of this image]
+</div><br clear=all />",
 
 # Statistics
 #